/*
读文件 排序 然后生成 name.txt read.txt info.txt
name.txt 一行一条序列名
read.txt 一行一条序列数据
info.txt 数据集的信息
用法:
makeDB -i 输入文件 -o 输出目录
2022-02-08 by 鞠震
*/
// 这不是重点 不用管效率 代码越简单越好
// 为支持TB级 不能把数据都读入内存 要先生成索引

#include <iostream>  // cout
#include <fstream>  // fstream
#include <iomanip>  // setprecision
#include <string>  // string
#include <vector>  // vector
#include <algorithm>  // sort
#include "timer.h"  // 计时器
#include "cmdline.h"  // 解析器
//--------数据--------//
struct Option {  // 输入选项
  std::string inputFile;  // 输入文件
  std::string outputDir;  // 输出文件的路径
};
struct Read {  // 记录一条序列的位置
  long offset;  // 序列的起始位置
  long nameLength;  // 序列名长度
  int readLength;  // 序列数据长度 最长65536
};
//--------函数--------//
// parse 解析输入选项
void parse(int argc, char **argv, Option &option) {
  cmdline::parser parser;  // 解析器
  parser.add<std::string>("input", 'i', "input file", true, "");
  parser.add<std::string>("output", 'o', "output directory", true, "");
  parser.parse_check(argc, argv);
  option.inputFile = parser.get<std::string>("input");
  option.outputDir = parser.get<std::string>("output");
}
// makeIndex 生成文件索引
void makeIndex(Option &option, std::vector<Read> &reads) {
  std::ifstream file(option.inputFile);  // 打开输入
  file.seekg(0, std::ios::end);
  long endPoint = file.tellg();  // 文件总长度
  file.seekg(0, std::ios::beg);
  std::string line;
  long logCount = 0;  // 打印进度
  while(file.peek() != EOF) {  // 读到文件结束
    Read read;
    // 读序列名
    read.offset = file.tellg();  // 序列名起始位置
    getline(file, line);  // 读序列名
    read.nameLength = line.size();  // 序列名长度
    read.readLength = 0;  // 序列数据长度清零
    // 读序列数据
    while (file.peek() != EOF && file.peek() != '>') {
      getline(file, line);
      read.readLength += line.size();
    }
    // 写入节点 打印进度
    if (read.readLength >= 16 && read.readLength <= 65536) {
      reads.push_back(read);  // 序列长度16-65536
    }
    if (logCount%(1000*100) == 0) std::cout << "\rMake index: "
      << std::setiosflags(std::ios::fixed) << std::setprecision(2)
      << (float)file.tellg()/(float)endPoint*100.0f << "%" << std::flush;
    logCount += 1;
  }
  std::cout << "\rMake index: 100.00%\n";
  std::cout << "Read " << logCount << " and " << reads.size() << " valid.\n";
  file.close();
  // 排序
  std::cout << "Sorting...";
  std::sort(reads.begin(), reads.end(), [](Read &a, Read &b) {
    return a.readLength > b.readLength;
  });
  std::cout << "finish\n";
}
// makeDB 生成数据库
void makeDB(Option &option, std::vector<Read> &reads) {
  std::ifstream file(option.inputFile);
  std::ofstream nameFile(option.outputDir+"/name.txt");  //存储序列名
  std::ofstream readFile(option.outputDir+"/read.txt");  //存储序列数据
  std::ofstream indexFile(option.outputDir+"/index.bin");  //存储序列索引
  std::ofstream infoFile(option.outputDir+"/info.txt");  //存储数据集信息
  long readsCount = reads.size();  // 序列条数
  std::vector<long> nameIndex(readsCount);  // 序列名索引
  std::vector<long> readIndex(readsCount);  // 序列数据索引
  std::vector<int> readLength(readsCount);  // 序列长度索引
  // 写索引和信息
  std::cout << "Make index...";
  long namePre=0, readPre=0;  // 前一条序列的位置
  for (long i=0; i<readsCount; i++) {
    nameIndex[i] = namePre;
    readIndex[i] = readPre;
    readLength[i] = reads[i].readLength;
    namePre += reads[i].nameLength+1;  // 换行符
    readPre += reads[i].readLength+1;
  }
  indexFile.write((char*)nameIndex.data(), readsCount*sizeof(long));
  indexFile.write((char*)readIndex.data(), readsCount*sizeof(long));
  indexFile.write((char*)readLength.data(), readsCount*sizeof(int));
  for (int i=0; i<4; i++) {
    std::cout << readLength[i] << " ";
  }
  std::cout << "\n";
  infoFile << readsCount << std::endl;  // 序列个数
  infoFile << readLength[0] << std::endl;  // 最长序列
  infoFile << readLength[readsCount-1] << std::endl;  // 最短序列
  infoFile << "readsCount longest shortest\n";
  std::cout << "finish\n";
  // 写数据和序列名
  std::string multiLine;  // 多行组成的序列数据
  std::string line;
  for (long i=0; i<readsCount; i++) {  // 遍历序列
    file.seekg(reads[i].offset, std::ios::beg);  // 移动到序列起始
    getline(file, line);
    nameFile << line+"\n";
    multiLine.clear();  // 用前先清空
    while (file.peek() != EOF && file.peek() != '>') {  // 把多行数据读成一行
      getline(file, line);
      multiLine += line;
    }
    readFile << multiLine+"\n";
    if (i%(1000*10) == 0) std::cout << "\rMake data base: "
      << std::setiosflags(std::ios::fixed) << std::setprecision(2)
      << (float)i/(float)readsCount*100.0f << "%" << std::flush;
  }
  std::cout << "\rMake data base: 100.00%\n";
  // 结尾
  file.close();
  nameFile.close();
  readFile.close();
  indexFile.close();
  infoFile.close();
}
//--------主函数--------//
int main(int argc, char **argv) {
  Timer timer; timer.start();  // 开始计时
  Option option;  // 输入选项
  parse(argc, argv, option);  // 解析输入选项
  std::cout << "Make data base begin:\t"; timer.getTimeNow();  // 开始时间戳
  std::vector<Read> reads;  // 序列长度与偏移
  makeIndex(option, reads);  // 生成文件索引
  makeDB(option, reads);  // 生成数据库
  std::cout << "Make data base finish:\t"; timer.getTimeNow();  // 结束时间戳
  std::cout << "Make data base total:\t"; timer.getDuration();  // 耗时
}
